今天接續上篇來介紹一下,假如在 validates
方法代入 options 作為 validator,有什麼應該注意的地方~
假設我建立一個對輸入 csv 的表頭進行驗證的 validator HeadersValidator
,想要驗證輸入的 csv 一定要有指定的欄位,我希望的使用方式像這樣:
validates :csv, headers: ["name", "age"]
那我要怎麼知道,我傳入的 ["name", "age"]
會被放在 validator 的哪裡?我在 validator 內要怎麼引用?
我們可以透過 initialize
來觀察看看,validates
這個 method 會怎麼 parse 我們輸入的設定。
# 先透過把傳入參數印出來,看看他到底會怎麼傳入。
class HeadersValidator < ActiveModel::EachValidator
def initialize(*args)
p args
end
end
class MyClass
include ActiveModel::Model
validates :csv, headers: ["name", "age"]
end
印出如下
[{:attributes=>[:csv], :in=>["name", "age"], :class=>MyClass}]
我們可以發現,initialize 傳入的 argument 只會有一個 Hash,且我們帶入的 array 被傳到了 :in
這個 key。
之所以會這樣,是因為在使用 validates
這方法時,rails 會根據傳入的 validator 底下的 value 去做對應的 pre-parse,把輸入的值整理成一包 options。
根據 source code 的 parse 規則如下:
如果傳入 hash: 照使用者輸入的原樣
ex:validates :csv, headers: { in: ["name", "age"] }
如果只給 true: 給一包空的{}
ex:validates :csv, headers: true
如果傳入 array 或是 range: 把 value 放在:in
變成{in: ["name", "age"]}
ex:validates :csv, headers: ["name", "age"]
其他的像是 string or regex 等等: 把 value 放在:with
變成{with: "string"}
ex:validates :csv, headers: "string"
而這包 options 會在排除掉 :attributes
以及 :class
兩個 key 之後,存放在 validator 的 @options
裡面。ActiveModel::Validator
有使用 attr_reader :options
建立 method,所以代表我們可以讀取實體變數,也可以使用 options
這個 method 去拉出當初輸入的這包 options。
所以我們的 validator 可以這樣寫:
class HeadersValidator < ActiveModel::EachValidator
def validate_each(record, attr_name, csv)
# 設定驗證的 csv 欄位要全部都有才算合格
return if (options[:in] & csv.headers) == options[:in]
record.errors.add(attr_name, :invalid_headers)
end
def check_validity!
# 這邊驗證當其他人使用這個 validator 時需要輸入 array
unless options[:in].is_a?(Array)
raise "should be array."
end
end
end
這樣,您的自訂 validator 就做好啦~
不知道讀者們會不會好奇一個問題,那就是很多的 validation 都是使用像是 validates_presence_of
這樣的 helper method 在進行設定的,那我要如何自訂一個 helper method 才能像上面的自訂驗證器一樣可以全專案共享呢?
下回見分曉~